const SerializedField: (defaultValue?: any) => PropertyDecorator = (defaultValue: any) => {
    return (target, key) => {
        const clz = target.constructor;
        if (!clz['properties']) {
            clz['properties'] = [];
        }
        clz['properties'].push({ key: key, defaultValue: defaultValue });
    }
}



class Behaviour {
    private static allBehaviours: { [uuid: number]: Behaviour } = {};
    private static behaviourUuidIndex = 1;

    static getBehaviour(uuid: number) {
        return this.allBehaviours[uuid];
    }

    uuid = 1;
    isDestroy = false;
    isExecuteOnStart = false;

    gameObject: GameObject = null;

    constructor() {
        this.uuid = Behaviour.behaviourUuidIndex++;
        Behaviour.allBehaviours[this.uuid] = this;
    }

    onAddToGameObject() {

    }

    onStart() {

    }
    onUpdate(duringTime: number) {

    }
    onDestroy() {

    }
}

class audioPlayer extends Behaviour{
    @SerializedField()
    audioUrl:string;
    mp3:HTMLAudioElement;
    @SerializedField(false)
    isloop:boolean;

    playAudio(){   
        if(this.audioUrl){
            this.mp3 = getAudio(this.audioUrl);
        }
        if(this.mp3){
            this.mp3.play();
        } 
    }
    onAddToGameObject(){
        this.gameObject.audioPlayer = this;
    }
    onUpdate(){
        if(this.mp3){
            if(this.mp3.ended&&this.isloop){
                this.mp3.play();
            }
        }
    
    }
    onDestroy(){
        if(this.gameObject.audioPlayer==this){
            this.gameObject.audioPlayer=null;
        }
    }
    
}


class Render extends Behaviour {
    // @SerializedField(100)
    // anchorX: number;
    // @SerializedField(100)
    // anchorY: number;

    private static allRender: Render[] = [];
    constructor() {
        super();
        Render.allRender.push(this);
    }
    static getAllRender() {
        return Render.allRender;
    }
    onAddToGameObject() {

        this.gameObject.renderer = this;

    }
    onDestroy() {
        if (this.gameObject.renderer == this) {
            this.gameObject.renderer = null;
        }
    }
}

/**图像
 * @param xy 位置
 * @param image 图像地址
 * @param height 高
 * @param width 宽
 */
class ImageRenderer extends Render {

    @SerializedField()
    imageUrl: string;
    image = null;
    @SerializedField(100)
    height: number;
    @SerializedField(100)
    width: number;
   

    hitTest(point) {

        if (!this.image) {
            return null;
        }
        const rectangle = { x:0, y: 0, width: this.width, height: this.height };
        const result = checkPointInRectangle(point, rectangle);
        if (result) {
            return this.gameObject;
        }
        else {
            return null;
        }
    }

    onDraw(context) {
        if (this.imageUrl) {
            this.image = getImage(this.imageUrl);
        }
        if (this.image) {
            context.drawImage(this.image, 0, 0, this.width, this.height);
        }
    }

}

/**文本
 * @param xy 位置
 * @param context 内容
 * @param font 字体
 * @param color 颜色
 */
class TextRenderer extends Render {
    @SerializedField()
    content: string;
    @SerializedField()
    font: string;
    @SerializedField("black")
    color: string;



    hitTest(point) {
        const rectangle = { x: 0, y: 0, width: 100, height: 20 };
        const result = checkPointInRectangle(point, rectangle);
        if (result) {
            return this.gameObject;
        }
        else {
            return null;
        }
    }

    onDraw(context) {
        context.fillStyle = this.color;
        context.font = this.font;
        context.fillText(this.content, 0, 0);
    }

}

/**线段
 * @param xy 起始位置
 * @param endxendy 结束位置
 * @param color 颜色
 */
class LineRenderer extends Render {
    @SerializedField(0)
    endx: number;
    @SerializedField(0)
    endy: number;
    @SerializedField("black")
    color: string;



    hitTest(point) {
        return null;
    }

    onDraw(context) {
        const transform = this.gameObject.getBehaviour(Transform);
        context.beginPath();
        context.moveTo(0, 0);
        context.strokeStyle = this.color;
        context.lineTo(this.endx - transform.x, this.endy - transform.y);
        context.stroke();
    }

}

/**矩形
 * @param xy 中心
 * @param width 宽
 * @param height 长
 * @param shapecolor 边颜色
 * @param fillcolor 填充颜色
 * @param angle 选转角度*/
class RectangleRenderer extends Render {
    @SerializedField(100)
    width: number;
    @SerializedField(100)
    height: number;
    @SerializedField("black")
    shapeColor: string;
    @SerializedField("black")
    fillColor: string;



    hitTest(point) {
        const rectangle = { x: -this.width / 2, y: -this.height / 2, width: this.width, height: this.height };
        const result = checkPointInRectangle(point, rectangle);
        if (result) {
            return this.gameObject;
        }
        else {
            return null;
        }
    }

    onDraw(context) {
        context.beginPath();
        context.moveTo(- this.width / 2, - this.height / 2);
        context.strokeStyle = this.shapeColor;
        context.fillStyle = this.fillColor;
        context.lineTo(this.width / 2, - this.height / 2);
        context.lineTo(this.width / 2, this.height / 2);
        context.lineTo(- this.width / 2, this.height / 2);
        context.lineTo(- this.width / 2, - this.height / 2);
        context.stroke();
        context.fill();
    }

}

/**圆
 * @param xy 圆心
 * @param radius 半径
 * @param shapeColor 颜色
 * @param percent 百分比
 */
class CricleRenderer extends Render {
    @SerializedField(10)
    radius: number;
    @SerializedField("black")
    shapeColor: string;
    @SerializedField(1)
    percent: number;



    hitTest(point) {
        return null;
    }

    onDraw(context) {
        context.beginPath();
        context.strokeStyle = this.shapeColor;
        context.arc(0, 0, this.radius, 0, Math.PI * 2 * this.percent, false);
        context.stroke();
    }

}



class Transform extends Behaviour {

    @SerializedField(0)
    x: number;
    @SerializedField(0)
    y: number;
    @SerializedField(1)
    scaleX: number;
    @SerializedField(1)
    scaleY: number;
    @SerializedField(0)
    rotation: number;
    @SerializedField(1)
    alpha: number;
    @SerializedField(100)
    anchorX: number;
    @SerializedField(100)
    anchorY: number;

    localMatrix;
    globalMatrix;

    globalAlpha = 1;

    constructor() {
        super();
        this.localMatrix = new Matrix();
        this.globalMatrix = new Matrix();
    }

    calculateGlobalProperties() {
        if (!this.gameObject.parent) {
            return;
        }
        const transform = new Matrix(1, 0,  0,  1,  -this.anchorX,  -this.anchorY); 
        const parentTransform = this.gameObject.parent.getBehaviour(Transform);
        this.localMatrix.updateFromDisplayObject(this.x+this.anchorX, this.y+this.anchorY, this.scaleX, this.scaleY, this.rotation);
        this.localMatrix = matrixAppendMatrix(transform,this.localMatrix);
        this.globalMatrix = matrixAppendMatrix(this.localMatrix, parentTransform.globalMatrix);
        
        this.globalAlpha = parentTransform.globalAlpha * this.alpha;
    }
}